﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using Common.Utilities;
using Common.Utilities.Dispatcher;

namespace Workflows.Components.TextMining.NamedEntities
{
    /// <summary>
    /// 
    /// </summary>
    public class OrganizationEncodingWorker:IWorker 
    {
        private Dictionary<string, int> _StopWords;
        private Dictionary<string, string> _Synonyms;
        private Dictionary<string, int> _AddressTokens;
        private Dictionary<string, int> _SchoolTokens;
        private Dictionary<string, int> _HealthTokens;
        private Dictionary<string, int> _CorpTokens;
        private Dictionary<string, int> _TypeTokens;
        private Dictionary<string, int> _NameTokens;
        private Dictionary<string, int> _DivisionTokens;
        private Dictionary<string, int> _RareTokens;

        #region Implementation of IWorker
        public event WorkerStarted WorkerStartedEvent;
        public event WorkerFinished WorkerFinishedEvent;
        public event WorkerHasError WorkerErredEvent;
        public event WorkerCancelled WorkerCancelledEvent;
        public event WorkerStatusChanged WorkerStatusChangedEvent;

        private Thread workerThread;
        private OrganizationEncodingInstruction instruction;
        private OrganizationEncodingOutput output;
        private RunContext _Context;
        private Guid _TicketID;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }

        /// <summary></summary>
        public Thread WorkerThread
        {
            get { return workerThread; }
        }
        /// <summary></summary>
        public InstructionBase Instruction
        {
            get { return instruction; }
        }
        /// <summary></summary>
        public ResultBase Output
        {
            get { return output; }
        }
        /// <summary></summary>
        public Type ComponentType
        {
            get { return typeof(OrganizationEncodingComponent); }
        }
        /// <summary></summary>
        public RunContext Context
        {
            get { return _Context; }
            set { _Context = value; }
        }
        /// <summary></summary>
        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._TicketID = ticketGuid;
            this.instruction = (OrganizationEncodingInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }
        /// <summary></summary>
        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._TicketID = ticketGuid;
            this.instruction = (OrganizationEncodingInstruction)request;
            this._Context = context;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }
        /// <summary></summary>
        public void DoWork()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this.instruction);
            }
            this.workerThread = Thread.CurrentThread;
            this.EncodeOrganizationNames();
        }
        /// <summary></summary>
        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this.instruction);
            }
            this.workerThread = new Thread(new ThreadStart(this.EncodeOrganizationNames));
            this.workerThread.Start();
        }
        /// <summary></summary>
        public void CancelWork()
        {
            if (this.workerThread != null && this.workerThread.IsAlive)
            {
                this.workerThread.Abort();
                this.workerThread.Join();

                if (this.WorkerCancelledEvent != null)
                {
                    this.WorkerCancelledEvent(this._TicketID, this.GetType(), this.instruction);
                }
            }
        }

        #endregion

        #region work
        private void InitTokens()
        {
            this._StopWords=new Dictionary<string, int>();
            string[] stopWords = Properties.Resources.EntityStopWords.Split(new char[]{'\n','\r'});
            foreach(string stopWord in stopWords)
            {
                if(stopWord.Trim().Length>0)
                    this._StopWords.Add(stopWord, this._StopWords.Count+1);
            }

            this._Synonyms=new Dictionary<string, string>();
            string[] lines1 = Properties.Resources.EntitySynonyms.Split(new char[] {'\n', '\r'});
            foreach(string line in lines1)
            {
                if(line.Trim().Length>0)
                {
                    string[] parts = line.Trim().ToLower().Split(new char[] {','});
                    string fullName = parts[0];
                    string alterName = parts[1];
                    this._Synonyms.Add(alterName,fullName);
                }
            }

            this._AddressTokens=new Dictionary<string, int>();
            this._SchoolTokens=new Dictionary<string, int>();
            this._HealthTokens=new Dictionary<string, int>();
            this._CorpTokens=new Dictionary<string, int>();
            this._TypeTokens=new Dictionary<string, int>();
            this._NameTokens=new Dictionary<string, int>();
            this._DivisionTokens=new Dictionary<string, int>();
            this._RareTokens=new Dictionary<string, int>();
            string[] lines2 = Properties.Resources.EntityFreqs.Split(new char[] {'\n', 'r'});
            foreach(string line in lines2)
            {
                if(line.Trim().Length>0)
                {
                    string[] parts = line.Trim().ToLower().Split(new char[] {','});
                    if (parts.Length == 3)
                    {
                        string phraseType = parts[0];
                        string word = parts[1];
                        if (phraseType == "address")
                            this._AddressTokens.Add(word, this._AddressTokens.Count + 1);
                        else if (phraseType == "school")
                            this._SchoolTokens.Add(word, this._SchoolTokens.Count + 1);
                        else if (phraseType == "corporation")
                            this._CorpTokens.Add(word, this._CorpTokens.Count + 1);
                        else if (phraseType == "health")
                            this._HealthTokens.Add(word, this._HealthTokens.Count + 1);
                        else if (phraseType == "type")
                            this._TypeTokens.Add(word, this._TypeTokens.Count + 1);
                        else if (phraseType == "name")
                            this._NameTokens.Add(word, this._NameTokens.Count + 1);
                        else if (phraseType == "division")
                            this._DivisionTokens.Add(word, this._DivisionTokens.Count + 1);
                    }
                }
            }
        }

        private void EncodeOrganizationNames()
        {
            try
            {
                this.InitTokens();
                int totalOrgCount = 0;
                int linkedOrgCount = 0;

                Dictionary<object,string> phraseTypeByKeys = new Dictionary<object, string>();
                Dictionary<object, string> wordSeqByKeys = new Dictionary<object, string>();
                DataTable dtOrgNames=new DataTable();
                if(string.IsNullOrEmpty(this.instruction.Sql))
                {
                    string sql = string.Format(
                        "select [{0}],[{1}] from {2}.[{3}]",
                        this.instruction.PKField, this.instruction.OrgNameField,
                        this.instruction.SchemaName, this.instruction.TableName);
                    dtOrgNames = DbUtil.GetTableDataBySQL(this.instruction.ConnStr, "OrgNames", sql);
                }
                else
                {
                    string sql = string.Format(
                        "select [{0}],[{1}] from ({2}) t",
                        this.instruction.PKField, this.instruction.OrgNameField,
                        this.instruction.Sql);
                    dtOrgNames = DbUtil.GetTableDataBySQL(this.instruction.ConnStr, "OrgNames", sql);
                }
                if(dtOrgNames==null || dtOrgNames.Rows.Count==0)
                    throw new Exception("Unable to get org name data from database");
                foreach(DataRow dr in dtOrgNames.Rows)
                {
                    if(dr[this.instruction.PKField]==null ||
                        dr[this.instruction.PKField]==DBNull.Value ||
                        dr[this.instruction.OrgNameField]==null ||
                        dr[this.instruction.OrgNameField]==DBNull.Value ||
                        dr[this.instruction.OrgNameField].ToString().Length ==0)
                        continue;

                    object pk = dr[this.instruction.PKField];
                    string orgName = dr[this.instruction.OrgNameField].ToString();
                    string phraseKey = string.Empty;
                    string wordKey = string.Empty;
                    this.EncodeOrganizationName(orgName,ref phraseKey, ref wordKey);
                    phraseTypeByKeys.Add(pk, phraseKey);
                    wordSeqByKeys.Add(pk, wordKey);
                }

                this.output=new OrganizationEncodingOutput(
                    this.instruction.OwnerComponentID,this._TicketID,
                    this.instruction.OutputTable, totalOrgCount,
                    linkedOrgCount);
                if (this.WorkerFinishedEvent != null)
                    this.WorkerFinishedEvent(this._TicketID, this.GetType(), this.instruction, this.output);
            }
            catch (Exception ex)
            {
                if (this.WorkerErredEvent != null)
                    this.WorkerErredEvent(this._TicketID, this.GetType(), this.instruction, ex.ToString(), true);
            }
        }
        #endregion

        private void EncodeOrganizationName(
            string orgName, ref string phraseTypeEncoded, 
            ref string wordSequenceEncoded)
        {
            phraseTypeEncoded = string.Empty;
            wordSequenceEncoded = string.Empty;

            foreach (string stopWord in this._StopWords.Keys)
                orgName = orgName.Replace(" " + stopWord + " ", " ");
            orgName = orgName.ToLower().Trim();
            while (orgName.IndexOf("  ") > 0)
                orgName = orgName.Replace("  ", " ");
            List<string> tokens=new List<string>();
            string[] words = orgName.Split(new char[] {' '});
            foreach(string word in words)
            {
                if(this._Synonyms.ContainsKey(word))
                    tokens.Add(this._Synonyms[word]);
                else 
                    tokens.Add(word);
            }
            foreach(string token in tokens)
            {
                if (phraseTypeEncoded.Length > 0)
                    phraseTypeEncoded += "-";
                if (wordSequenceEncoded.Length > 0)
                    wordSequenceEncoded += "-";
        
                if(this._AddressTokens.ContainsKey(token))
                {
                    phraseTypeEncoded += "1";
                    wordSequenceEncoded += this._AddressTokens[token].ToString();
                }
                else if(this._TypeTokens.ContainsKey(token))
                {
                    phraseTypeEncoded += "2";
                    wordSequenceEncoded += this._TypeTokens[token].ToString();
                }
                else if(this._NameTokens.ContainsKey(token))
                {
                    phraseTypeEncoded += "3";
                    wordSequenceEncoded += this._NameTokens[token].ToString();
                }
                else if(this._SchoolTokens.ContainsKey(token))
                {
                    phraseTypeEncoded += "4";
                    wordSequenceEncoded += this._SchoolTokens[token].ToString();
                }
                else if(this._HealthTokens.ContainsKey(token))
                {
                    phraseTypeEncoded += "5";
                    wordSequenceEncoded += this._HealthTokens[token].ToString();
                }
                else if(this._CorpTokens.ContainsKey(token))
                {
                    phraseTypeEncoded += "6";
                    wordSequenceEncoded += this._CorpTokens[token].ToString();
                }
                else if(this._DivisionTokens.ContainsKey(token))
                {
                    phraseTypeEncoded += "7";
                    wordSequenceEncoded += this._DivisionTokens[token].ToString();
                }
                else if(this._RareTokens.ContainsKey(token))
                {
                    phraseTypeEncoded += "8";
                    wordSequenceEncoded += this._RareTokens[token].ToString();
                }
                else
                {
                    phraseTypeEncoded += "8";
                    this._RareTokens.Add(token, this._RareTokens.Count + 1);
                    wordSequenceEncoded += this._RareTokens[token].ToString();
                }
            }
        }

        private double SimilarityByEncoding(string phraseKey1, string wordKey1,
            string phraseKey2, string wordKey2)
        {
            double score = 0;
            double penalty = 0;

            string commonPhraseKey=string.Empty;
            int phraseSubLen = StringUtil.LongestCommonSubsequence(phraseKey1, phraseKey2, ref commonPhraseKey);
            string commonWordKey = string.Empty;
            int wordSubLen = StringUtil.LongestCommonSubsequence(phraseKey1, phraseKey2, ref commonWordKey);
            if (phraseSubLen > 0 && wordSubLen > 0)
            {
                string[] commonPhrases = commonPhraseKey.Split(new char[] { '-' });
                string[] words1 = wordKey1.Split(new char[] {'-'});
                string[] words2 = wordKey2.Split(new char[] {'-'});
                string[] phrases1 = phraseKey1.Split(new char[] {'-'});
                string[] phrases2 = phraseKey2.Split(new char[] {'-'});
                foreach(string commonPhrase in commonPhrases)
                {
                    List<string> commonWords1=new List<string>();
                    List<string> commonWords2 = new List<string>();
                    for(int i=0;i<phrases1.Length;i++)
                    {
                        if(phrases1[i]==commonPhrase)
                        {
                            if (!commonWords1.Contains(words1[i]))
                                commonWords1.Add(words1[i]);
                        }
                    }
                    for(int i=0;i<phrases2.Length;i++)
                    {
                        if (phrases2[i] == commonPhrase)
                            if (!commonWords2.Contains(words2[i]))
                                commonWords2.Add(words2[i]);
                    }
                    string[] sharedWords = StringUtil.Intersection(commonWords1.ToArray(), commonWords2.ToArray());
                    score += this.instruction.OrgEntityWeights[commonPhrase]*((double) sharedWords.Length)/
                             (Math.Max(commonWords1.Count, commonWords2.Count));
                }

                double penalty1 = 0;
                foreach(string phrase1 in phrases1)
                {
                    bool shared = false;
                    foreach (string commonPhrase in commonPhrases)
                    {
                        if(commonPhrase==phrase1)
                        {
                            shared = true;
                            break;
                        }
                    }
                    if (!shared)
                        penalty1 += this.instruction.OrgEntityPenalties[phrase1];
                }
                double penalty2 = 0;
                foreach(string phrase2 in phrases2)
                {
                    bool shared = false;
                    foreach(string commonPhrase in commonPhrases)
                    {
                        if(commonPhrase==phrase2)
                        {
                            shared = true;
                            break;
                        }
                    }
                    if (!shared)
                        penalty2 += this.instruction.OrgEntityPenalties[phrase2];
                }
                penalty = Math.Min(penalty1, penalty2);
                return score + penalty;
            }
            else
                return 0;
        }
    }
}
